#include <dlfcn.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/syscall.h>

#include "debug.h"
#include "box64stack.h"
#include "x64emu.h"
#include "x64run.h"
#include "x64emu_private.h"
#include "x64run_private.h"
//#include "x64primop.h"
#include "x64trace.h"
//#include "myalign.h"
#include "box64context.h"
#include "callback.h"
#include "signals.h"
#include "x64tls.h"

extern int (*host_signal_notify)(void*);

enum qemu_host_event_t
{
    QEMU_HOST_UNKNOWN = 0,
    QEMU_HOST_DLOPEN,
    QEMU_HOST_DLSYM,
    QEMU_HOST_DLCLOSE,
    QEMU_HOST_MMAN,
    QEMU_HOST_SIGNAL_NOTIFY,
    QEMU_HOST_SIGNAL_CALLBACK,
};

struct qemu_event_context_t
{
    x64emu_t *emu;
    union { enum qemu_host_event_t event; void* unused; };
};

extern void* my_mmap(x64emu_t* emu, void *addr, unsigned long length, int prot, int flags, int fd, int64_t offset);
extern int my_munmap(x64emu_t* emu, void* addr, unsigned long length);
extern int my_mprotect(x64emu_t* emu, void *addr, unsigned long len, int prot);
extern void user_sigactionhandler(void* args);

static void *target_mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset)
{
    return my_mmap(NULL, addr, len, prot, flags, fd, (int64_t)offset);
}

static int target_munmap(void* addr, unsigned long len)
{
    return my_munmap(NULL, addr, len);
}

static int target_mprotect(void *addr, unsigned long len, int prot)
{
    return my_mprotect(NULL, addr, len, prot);
}

#define target_madvise madvise

extern int my_pthread_create(x64emu_t *emu, void* t, void* attr, void* start_routine, void* arg);

static void qemu_host_handle_event(struct qemu_event_context_t* $context)
{
    switch($context->event) {
        case QEMU_HOST_DLOPEN: {
            struct { struct qemu_event_context_t $context; const char* name; void* $ret; } *_ = (typeof(_))$context;
            _->$ret = dlopen(_->name, 2);
            break;
        }
        case QEMU_HOST_DLSYM: {
            struct { struct qemu_event_context_t $context; void* handle; const char* name; void* $ret; } *_ = (typeof(_))$context;
            _->$ret = dlsym(_->handle, _->name);
            break;
        }
        case QEMU_HOST_DLCLOSE: {
            struct { struct qemu_event_context_t $context; void* handle; } *_ = (typeof(_))$context;
            dlclose(_->handle);
            break;
        }
        case QEMU_HOST_MMAN: {
            struct { struct qemu_event_context_t $context; void *p_mmap, *p_munmap, *p_mprotect, *p_madvise; } *_ = (typeof(_))$context;
            _->p_mmap     = &target_mmap;
            _->p_munmap   = &target_munmap;
            _->p_mprotect = &target_mprotect;
            _->p_madvise  = &target_madvise;
            break;
        }
        case QEMU_HOST_SIGNAL_NOTIFY: {
            struct { struct qemu_event_context_t $context; void* handle; } *_ = (typeof(_))$context;
            host_signal_notify = (typeof(host_signal_notify))_->handle;
            break;
        }
        case QEMU_HOST_SIGNAL_CALLBACK: {
            struct { struct qemu_event_context_t $context; void* args; } *_ = (typeof(_))$context;
            user_sigactionhandler(_->args);
            break;
        }
        default:
            break;
    }
}

__attribute__((visibility("default"))) void my_qemu_host_direct_call(void* arg)
{
    typeof(qemu_host_handle_event)* func = *(typeof(func)*)arg;
    if(func) {
        (*func)((struct qemu_event_context_t*)arg);
    } else {
        *(void**)arg = &qemu_host_handle_event;
    }
}